#include "alloc.h"
#include "context.h"
#include "varint.h"
#include "pattern.h"
#include "array.h"
#include "proto.h"
#include "map.h"

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>

static void set_default_v(void* output, int ctype, pbc_var defv) {
  switch (ctype) {
    case CTYPE_INT32:
      *(uint32_t*)output = defv->integer.low;
      break;
    case CTYPE_INT64:
      *(uint64_t*)output = (uint64_t)defv->integer.low | (uint64_t)defv->integer.hi << 32;
      break;
    case CTYPE_DOUBLE:
      *(double*)output = defv->real;
      break;
    case CTYPE_FLOAT:
      *(float*)output = (float)defv->real;
      break;
    case CTYPE_BOOL:
      *(bool*)output = (defv->integer.low != 0);
      break;
    case CTYPE_INT8:
      *(uint8_t*)output = (uint8_t)defv->integer.low;
      break;
    case CTYPE_INT16:
      *(uint16_t*)output = (uint16_t)defv->integer.low;
      break;
    case CTYPE_VAR:
      *(_pbc_var*)output = *defv;
      break;
  }
}

static void _pattern_set_default(_pattern_field* field, char* output) {
  if (field->ctype == CTYPE_ARRAY || field->ctype == CTYPE_PACKED) {
    _pbc_array* array = (_pbc_array*)(output + field->offset);
    _pbcA_open(array);
  } else if (field->ptype == PTYPE_ENUM) {
    pbc_var defv;
    defv->integer.low = field->defv->e.id;
    defv->integer.hi = 0;
    set_default_v(output + field->offset, field->ctype, defv);
  }
  set_default_v(output + field->offset, field->ctype, field->defv);
}

void pbc_pattern_set_default(pbc_pattern* pat, void* output) {
  int i;
  for (i = 0; i < pat->count; i++) {
    _pattern_set_default(&pat->f[i], (char*)output);
  }
}

// pattern unpack

static _pattern_field* bsearch_pattern(pbc_pattern* pat, int id) {
  int begin = 0;
  int end = pat->count;
  while (begin < end) {
    int mid = (begin + end) / 2;
    _pattern_field* f = &pat->f[mid];
    if (id == f->id) {
      return f;
    }
    if (id < f->id) {
      end = mid;
    } else {
      begin = mid + 1;
    }
  }
  return NULL;
}

static inline int write_real(int ctype, double v, void* out) {
  switch (ctype) {
    case CTYPE_DOUBLE:
      *(double*)out = v;
      return 0;
    case CTYPE_FLOAT:
      *(float*)out = (float)v;
      return 0;
    case CTYPE_VAR:
      ((_pbc_var*)out)->real = v;
      return 0;
  }
  return -1;
}

static inline int write_longlong(int ctype, longlong* i, void* out) {
  switch (ctype) {
    case CTYPE_INT32:
      *(uint32_t*)out = i->low;
      return 0;
    case CTYPE_INT64:
      *(uint64_t*)out = (uint64_t)i->low | (uint64_t)i->hi << 32;
      return 0;
    case CTYPE_BOOL:
      *(bool*)out = (i->low != 0);
      return 0;
    case CTYPE_INT8:
      *(uint8_t*)out = (uint8_t)i->low;
      return 0;
    case CTYPE_INT16:
      *(uint8_t*)out = (uint16_t)i->low;
      return 0;
    case CTYPE_VAR:
      ((_pbc_var*)out)->integer = *i;
      return 0;
  }
  return -1;
}

static inline int write_integer(int ctype, atom* a, void* out) {
  return write_longlong(ctype, &(a->v.i), out);
}

static int unpack_array(int ptype, char* buffer, atom*, pbc_array _array);

int _pbcP_unpack_packed(uint8_t* buffer, int size, int ptype, pbc_array array) {
  pbc_var var;
  var->integer.hi = 0;
  int i;
  switch (ptype) {
    case PTYPE_DOUBLE:
      if (size % 8 != 0)
        return -1;
      for (i = 0; i < size; i += 8) {
        union {
          double d;
          uint64_t i64;
        } u;
        u.i64 = (uint64_t)buffer[i] | (uint64_t)buffer[i + 1] << 8 | (uint64_t)buffer[i + 2] << 16 |
                (uint64_t)buffer[i + 3] << 24 | (uint64_t)buffer[i + 4] << 32 | (uint64_t)buffer[i + 5] << 40 |
                (uint64_t)buffer[i + 6] << 48 | (uint64_t)buffer[i + 7] << 56;
        var->real = u.d;
        _pbcA_push(array, var);
      }
      return size / 8;
    case PTYPE_FLOAT:
      if (size % 4 != 0)
        return -1;
      for (i = 0; i < size; i += 4) {
        union {
          float f;
          uint32_t i32;
        } u;
        u.i32 = (uint32_t)buffer[i] | (uint32_t)buffer[i + 1] << 8 | (uint32_t)buffer[i + 2] << 16 |
                (uint32_t)buffer[i + 3] << 24;
        var->real = (double)u.f;
        _pbcA_push(array, var);
      }
      return size / 4;
    case PTYPE_FIXED32:
    case PTYPE_SFIXED32:
      if (size % 4 != 0)
        return -1;
      for (i = 0; i < size; i += 4) {
        var->integer.low = (uint32_t)buffer[i] | (uint32_t)buffer[i + 1] << 8 | (uint32_t)buffer[i + 2] << 16 |
                           (uint32_t)buffer[i + 3] << 24;
        _pbcA_push(array, var);
      }
      return size / 4;
    case PTYPE_FIXED64:
    case PTYPE_SFIXED64:
      if (size % 8 != 0)
        return -1;
      for (i = 0; i < size; i += 8) {
        var->integer.low = (uint32_t)buffer[i] | (uint32_t)buffer[i + 1] << 8 | (uint32_t)buffer[i + 2] << 16 |
                           (uint32_t)buffer[i + 3] << 24;
        var->integer.hi = (uint32_t)buffer[i + 4] | (uint32_t)buffer[i + 5] << 8 | (uint32_t)buffer[i + 6] << 16 |
                          (uint32_t)buffer[i + 7] << 24;
        _pbcA_push(array, var);
      }
      return size / 8;
    case PTYPE_INT64:
    case PTYPE_UINT64:
    case PTYPE_INT32:
    case PTYPE_UINT32:
    case PTYPE_ENUM: // enum must be integer type in pattern mode
    case PTYPE_BOOL: {
      int n = 0;
      while (size > 0) {
        int len;
        if (size >= 10) {
          len = _pbcV_decode(buffer, &(var->integer));
        } else {
          uint8_t temp[10];
          memcpy(temp, buffer, size);
          len = _pbcV_decode(buffer, &(var->integer));
          if (len > size)
            return -1;
        }
        _pbcA_push(array, var);
        buffer += len;
        size -= len;
        ++n;
      }
      return n;
    }
    case PTYPE_SINT32: {
      int n = 0;
      while (size > 0) {
        int len;
        if (size >= 10) {
          len = _pbcV_decode(buffer, &(var->integer));
          _pbcV_dezigzag32(&(var->integer));
        } else {
          uint8_t temp[10];
          memcpy(temp, buffer, size);
          len = _pbcV_decode(buffer, &(var->integer));
          if (len > size)
            return -1;
          _pbcV_dezigzag32(&(var->integer));
        }
        _pbcA_push(array, var);
        buffer += len;
        size -= len;
        ++n;
      }
      return n;
    }
    case PTYPE_SINT64: {
      int n = 0;
      while (size > 0) {
        int len;
        if (size >= 10) {
          len = _pbcV_decode(buffer, &(var->integer));
          _pbcV_dezigzag64(&(var->integer));
        } else {
          uint8_t temp[10];
          memcpy(temp, buffer, size);
          len = _pbcV_decode(buffer, &(var->integer));
          if (len > size)
            return -1;
          _pbcV_dezigzag64(&(var->integer));
        }
        _pbcA_push(array, var);
        buffer += len;
        size -= len;
        ++n;
      }
      return n;
    }
  }
  return -1;
}

static int unpack_field(int ctype, int ptype, char* buffer, atom* a, void* out) {
  if (ctype == CTYPE_ARRAY) {
    return unpack_array(ptype, buffer, a, (_pbc_array*)out);
  }
  if (ctype == CTYPE_PACKED) {
    return _pbcP_unpack_packed(
        (uint8_t*)buffer + a->v.s.start, a->v.s.end - a->v.s.start, ptype, (_pbc_array*)out);
  }
  switch (ptype) {
    case PTYPE_DOUBLE:
      CHECK_BIT64(a, -1);
      return write_real(ctype, read_double(a), out);
    case PTYPE_FLOAT:
      CHECK_BIT32(a, -1);
      return write_real(ctype, read_float(a), out);
    case PTYPE_INT64:
    case PTYPE_UINT64:
    case PTYPE_INT32:
    case PTYPE_UINT32:
    case PTYPE_ENUM: // enum must be integer type in pattern mode
    case PTYPE_BOOL:
      CHECK_VARINT(a, -1);
      return write_integer(ctype, a, out);
    case PTYPE_FIXED32:
    case PTYPE_SFIXED32:
      CHECK_BIT32(a, -1);
      return write_integer(ctype, a, out);
    case PTYPE_FIXED64:
    case PTYPE_SFIXED64:
      CHECK_BIT64(a, -1);
      return write_integer(ctype, a, out);
    case PTYPE_SINT32: {
      CHECK_VARINT(a, -1);
      longlong temp = a->v.i;
      _pbcV_dezigzag32(&temp);
      return write_longlong(ctype, &temp, out);
    }
    case PTYPE_SINT64: {
      CHECK_LEND(a, -1);
      longlong temp = a->v.i;
      _pbcV_dezigzag64(&temp);
      return write_longlong(ctype, &temp, out);
    }
    case PTYPE_MESSAGE:
      CHECK_LEND(a, -1);
      ((_pbc_var*)out)->m.buffer = buffer + a->v.s.start;
      ((_pbc_var*)out)->m.len = a->v.s.end - a->v.s.start;
      return 0;
    case PTYPE_STRING:
    case PTYPE_BYTES:
      CHECK_LEND(a, -1);
      ((pbc_slice*)out)->buffer = buffer + a->v.s.start;
      ((pbc_slice*)out)->len = a->v.s.end - a->v.s.start;
      return 0;
  }
  return -1;
}

static int unpack_array(int ptype, char* buffer, atom* a, pbc_array _array) {
  pbc_var var;
  int r = unpack_field(CTYPE_VAR, ptype, buffer, a, var);
  if (r != 0)
    return r;
  _pbcA_push(_array, var);

  return 0;
}

void pbc_pattern_close_arrays(pbc_pattern* pat, void* data) {
  int i;
  for (i = 0; i < pat->count; i++) {
    if (pat->f[i].ctype == CTYPE_ARRAY || pat->f[i].ctype == CTYPE_PACKED) {
      void* array = (char*)data + pat->f[i].offset;
      _pbcA_close((_pbc_array*)array);
    }
  }
}

static inline int _pack_wiretype(uint32_t wt, pbc_slice* s) {
  int len;
  if (s->len < 10) {
    uint8_t temp[10];
    len = _pbcV_encode32(wt, temp);
    if (len > s->len)
      return -1;
    memcpy(s->buffer, temp, len);
  } else {
    len = _pbcV_encode32(wt, (uint8_t*)s->buffer);
  }
  s->buffer = (char*)s->buffer + len;
  s->len -= len;
  return len;
}

static inline int _pack_varint64(uint64_t i64, pbc_slice* s) {
  int len;
  if (s->len < 10) {
    uint8_t temp[10];
    len = _pbcV_encode(i64, temp);
    if (len > s->len)
      return -1;
    memcpy(s->buffer, temp, len);
  } else {
    len = _pbcV_encode(i64, (uint8_t*)s->buffer);
  }
  s->buffer = (char*)s->buffer + len;
  s->len -= len;
  return len;
}

static inline int _pack_sint32(uint32_t v, pbc_slice* s) {
  int len;
  if (s->len < 10) {
    uint8_t temp[10];
    len = _pbcV_zigzag32(v, temp);
    if (len > s->len)
      return -1;
    memcpy(s->buffer, temp, len);
  } else {
    len = _pbcV_zigzag32(v, (uint8_t*)s->buffer);
  }
  s->buffer = (char*)s->buffer + len;
  s->len -= len;
  return len;
}

static inline int _pack_sint64(uint64_t v, pbc_slice* s) {
  int len;
  if (s->len < 10) {
    uint8_t temp[10];
    len = _pbcV_zigzag(v, temp);
    if (len > s->len)
      return -1;
    memcpy(s->buffer, temp, len);
  } else {
    len = _pbcV_zigzag(v, (uint8_t*)s->buffer);
  }
  s->buffer = (char*)s->buffer + len;
  s->len -= len;
  return len;
}

static inline void _fix32_encode(uint32_t v, uint8_t* buffer) {
  buffer[0] = (uint8_t)v;
  buffer[1] = (uint8_t)(v >> 8);
  buffer[2] = (uint8_t)(v >> 16);
  buffer[3] = (uint8_t)(v >> 24);
}

static inline void _fix64_encode(longlong* v, uint8_t* buffer) {
  _fix32_encode(v->low, buffer);
  _fix32_encode(v->hi, buffer + 4);
}

static int _pack_number(int ptype, int ctype, pbc_slice* s, void* input) {
  pbc_var var;
  if (ctype == CTYPE_VAR) {
    memcpy(var, input, sizeof(var));
  } else {
    switch (ctype) {
      case CTYPE_INT32:
        var->integer.low = *(uint32_t*)input;
        var->integer.hi = 0;
        break;
      case CTYPE_INT64: {
        uint64_t v = *(uint64_t*)input;
        var->integer.low = (uint32_t)(v & 0xffffffff);
        var->integer.hi = (uint32_t)(v >> 32);
        break;
      }
      case CTYPE_INT16:
        var->integer.low = *(uint16_t*)input;
        var->integer.hi = 0;
        break;
      case CTYPE_INT8:
        var->integer.low = *(uint8_t*)input;
        var->integer.hi = 0;
        break;
      case CTYPE_BOOL:
        var->integer.low = *(bool*)input;
        var->integer.hi = 0;
        break;
      case CTYPE_DOUBLE:
        var->real = *(double*)input;
        break;
      case CTYPE_FLOAT:
        var->real = *(float*)input;
        break;
    }
  }

  switch (ptype) {
    case PTYPE_FIXED64:
    case PTYPE_SFIXED64:
      if (s->len < 8)
        return -1;
      _fix64_encode(&(var->integer), (uint8_t*)s->buffer);
      s->buffer = (char*)s->buffer + 8;
      s->len -= 8;
      return 8;
    case PTYPE_DOUBLE:
      if (s->len < 8)
        return -1;
      double_encode(var->real, (uint8_t*)s->buffer);
      s->buffer = (char*)s->buffer + 8;
      s->len -= 8;
      return 8;
    case PTYPE_FLOAT:
      if (s->len < 4)
        return -1;
      float_encode((float)var->real, (uint8_t*)s->buffer);
      s->buffer = (char*)s->buffer + 4;
      s->len -= 4;
      return 4;
    case PTYPE_FIXED32:
    case PTYPE_SFIXED32:
      if (s->len < 4)
        return -1;
      _fix32_encode(var->integer.low, (uint8_t*)s->buffer);
      s->buffer = (char*)s->buffer + 4;
      s->len -= 4;
      return 4;
    case PTYPE_UINT64:
    case PTYPE_INT64:
    case PTYPE_INT32:
      return _pack_varint64((uint64_t)var->integer.low | (uint64_t)var->integer.hi << 32, s);
    case PTYPE_UINT32:
    case PTYPE_BOOL:
    case PTYPE_ENUM:
      return _pack_wiretype(var->integer.low, s);
    case PTYPE_SINT32:
      return _pack_sint32(var->integer.low, s);
    case PTYPE_SINT64:
      return _pack_sint64((uint64_t)var->integer.low | (uint64_t)var->integer.hi << 32, s);
    default:
      return -1;
  }
}

// s is the buffer to store pb binary data, s->len will decrease and s->buffer will increase when store data in s
// input is the Struct field pointer which points to the value, type of that value is ctype
// when input is a pbc_slice, pbc_slice.len < 0 means add more len bytes after it
// return the length of store data
static int _pack_field(_pattern_field* pf, int ctype, pbc_slice* s, void* input) {
  int wiretype;
  int ret = 0;
  int len;
  pbc_slice* input_slice;
  pbc_slice string_slice;

  switch (pf->ptype) {
    case PTYPE_FIXED64:
    case PTYPE_SFIXED64:
    case PTYPE_DOUBLE:
      wiretype = WT_BIT64;
      goto _number;
    case PTYPE_FIXED32:
    case PTYPE_SFIXED32:
    case PTYPE_FLOAT:
      wiretype = WT_BIT32;
      goto _number;
    case PTYPE_UINT64:
    case PTYPE_INT64:
    case PTYPE_INT32:
    case PTYPE_BOOL:
    case PTYPE_UINT32:
    case PTYPE_ENUM:
    case PTYPE_SINT32:
    case PTYPE_SINT64:
      wiretype = WT_VARINT;
      goto _number;
    case PTYPE_STRING:
      input_slice = (pbc_slice*)input;
      if (input_slice->len < 0) {
        string_slice.buffer = input_slice->buffer;
        string_slice.len = (int)(strlen((const char*)string_slice.buffer) - input_slice->len);
        input_slice = &string_slice;
      }
      goto _string;
    case PTYPE_MESSAGE:
    case PTYPE_BYTES:
      input_slice = (pbc_slice*)input;
      goto _string;
    default:
      break;
  }

  return 0;
_string:
  wiretype = WT_LEND;
  len = _pack_wiretype(pf->id << 3 | wiretype, s);
  if (len < 0) {
    return len;
  }
  ret += len;
  len = _pack_wiretype(input_slice->len, s);
  if (len < 0) {
    return len;
  }
  ret += len;
  if (input_slice->len > s->len)
    return -1;
  memcpy(s->buffer, input_slice->buffer, input_slice->len);
  ret += input_slice->len;
  s->buffer = (char*)s->buffer + input_slice->len;
  s->len -= input_slice->len;

  return ret;
_number:
  len = _pack_wiretype(pf->id << 3 | wiretype, s);
  if (len < 0) {
    return len;
  }
  ret += len;
  len = _pack_number(pf->ptype, ctype, s, input);
  if (len < 0) {
    return len;
  }
  ret += len;

  return ret;
}

static int _pack_repeated(_pattern_field* pf, pbc_slice* s, pbc_array array) {
  int n = pbc_array_size(array);
  int ret = 0;
  if (n > 0) {
    int i;
    for (i = 0; i < n; i++) {
      int len = _pack_field(pf, CTYPE_VAR, s, _pbcA_index_p(array, i));
      if (len < 0)
        return len;
      ret += len;
    }
  }
  return ret;
}

static int _pack_packed_fixed(_pattern_field* pf, int width, pbc_slice* s, pbc_array array) {
  int len;
  int n = pbc_array_size(array);
  len = _pack_wiretype(n * width, s);
  if (len < 0) {
    return len;
  }
  if (s->len - len < n * width)
    return -1;
  int i;
  for (i = 0; i < n; i++) {
    assert(_pack_number(pf->ptype, CTYPE_VAR, s, _pbcA_index_p(array, i)) == width);
  }

  return len + n * width;
}

static int _pack_packed_varint(_pattern_field* pf, pbc_slice* slice, pbc_array array) {
  pbc_slice s = *slice;
  int n = pbc_array_size(array);
  int estimate = n;
  int estimate_len = _pack_wiretype(estimate, &s);
  if (estimate_len < 0) {
    return -1;
  }
  int i;
  int packed_len = 0;
  for (i = 0; i < n; i++) {
    int len = _pack_number(pf->ptype, CTYPE_VAR, &s, _pbcA_index_p(array, i));
    if (len < 0)
      return -1;
    packed_len += len;
  }
  if (packed_len == estimate) {
    *slice = s;
    return packed_len + estimate_len;
  }
  uint8_t temp[10];
  pbc_slice header_slice = {temp, 10};
  int header_len = _pack_wiretype(packed_len, &header_slice);
  if (header_len == estimate_len) {
    memcpy(slice->buffer, temp, header_len);
    *slice = s;
    return packed_len + estimate_len;
  }
  if (header_len + packed_len > slice->len)
    return -1;
  memmove((char*)slice->buffer + header_len, (char*)slice->buffer + estimate_len, packed_len);
  memcpy(slice->buffer, temp, header_len);
  slice->buffer = (char*)slice->buffer + packed_len + header_len;
  slice->len -= packed_len + header_len;
  return packed_len + header_len;
}

static int _pack_packed(_pattern_field* pf, pbc_slice* s, pbc_array array) {
  int n = pbc_array_size(array);
  if (n == 0)
    return 0;

  int ret = 0;
  int len;
  len = _pack_wiretype(pf->id << 3 | WT_LEND, s);
  if (len < 0) {
    return len;
  }
  ret += len;

  switch (pf->ptype) {
    case PTYPE_FIXED64:
    case PTYPE_SFIXED64:
    case PTYPE_DOUBLE:
      len = _pack_packed_fixed(pf, 8, s, array);
      if (len < 0)
        return len;
      break;
    case PTYPE_FIXED32:
    case PTYPE_SFIXED32:
    case PTYPE_FLOAT:
      len = _pack_packed_fixed(pf, 4, s, array);
      if (len < 0)
        return len;
      break;
    case PTYPE_UINT64:
    case PTYPE_INT64:
    case PTYPE_INT32:
    case PTYPE_BOOL:
    case PTYPE_UINT32:
    case PTYPE_ENUM:
    case PTYPE_SINT32:
    case PTYPE_SINT64:
      len = _pack_packed_varint(pf, s, array);
      if (len < 0)
        return len;
      break;
  }
  ret += len;

  return ret;
}

static bool _is_default(_pattern_field* pf, void* in) {
  switch (pf->ctype) {
    case CTYPE_INT64: {
      longlong* d64 = &pf->defv->integer;
      return ((uint64_t)d64->low | (uint64_t)d64->hi << 32) == *(uint64_t*)in;
    }
    case CTYPE_DOUBLE:
      return pf->defv->real == *(double*)in;
    case CTYPE_FLOAT:
      return (float)(pf->defv->real) == *(float*)in;
    case CTYPE_INT32:
      return pf->defv->integer.low == *(uint32_t*)in;
    case CTYPE_INT16:
      return (uint16_t)(pf->defv->integer.low) == *(uint16_t*)in;
    case CTYPE_INT8:
      return (uint8_t)(pf->defv->integer.low) == *(uint8_t*)in;
    case CTYPE_BOOL:
      if (pf->defv->integer.low)
        return *(bool*)in == true;
      else
        return *(bool*)in == false;
  }
  if (pf->ptype == PTYPE_STRING) {
    pbc_slice* slice = (pbc_slice*)in;
    if (slice->buffer == NULL) {
      return pf->defv->s.str[0] == '\0';
    }
    int len = slice->len;
    if (len <= 0) {
      return strcmp(pf->defv->s.str, (const char*)slice->buffer) == 0;
    }
    return len == pf->defv->s.len && memcmp(pf->defv->s.str, slice->buffer, len) == 0;
  } else if (pf->ptype == PTYPE_BYTES) {
    pbc_slice* slice = (pbc_slice*)in;
    if (slice->buffer == NULL)
      return true;
  }

  return false;
}

// input is a pointer points to a Struct, field offset set in pat
// s is a slice which is the buffer for store packed pb binary
// s->len will store the length of the data which store in s->buffer
// return the length of the rest
int pbc_pattern_pack(pbc_pattern* pat, void* input, pbc_slice* s) {
  pbc_slice slice = *s; // copy s->buffer and s->len
  int i;
  for (i = 0; i < pat->count; i++) {
    _pattern_field* pf = &pat->f[i];
    void* in = (char*)input + pf->offset;
    int len = 0;
    switch (pf->label) {
      case LABEL_OPTIONAL:
        if (_is_default(pf, in)) {
          break;
        }
      case LABEL_REQUIRED:
        len = _pack_field(pf, pf->ctype, &slice, in); // slice will be changed
        break;
      case LABEL_REPEATED:
        len = _pack_repeated(pf, &slice, (_pbc_array*)in);
        break;
      case LABEL_PACKED:
        len = _pack_packed(pf, &slice, (_pbc_array*)in);
        break;
    }
    if (len < 0) {
      return len;
    }
  }
  // slice.buffer points to the rest of the buffer
  int len = (int)((char*)slice.buffer - (char*)s->buffer);
  int ret = s->len - len;
  s->len = len;
  return ret;
}

// parsing one layer only, treat Message as String, String slice points to pb binary itself
int pbc_pattern_unpack(pbc_pattern* pat, pbc_slice* s, void* output) {
  if (s->len == 0) {
    pbc_pattern_set_default(pat, output);
    return 0;
  }
  pbc_ctx _ctx;
  int r = _pbcC_open(_ctx, s->buffer, s->len);
  if (r <= 0) {
    pat->env->lasterror = "Pattern unpack open context error";
    _pbcC_close(_ctx);
    return r - 1;
  }

  context* ctx = (context*)_ctx; // key-value pair list
  bool* field = (bool*)alloca(pat->count * sizeof(bool));
  memset(field, 0, pat->count * sizeof(bool));

  int i;
  int fc = 0; // field count

  for (i = 0; i < ctx->number; i++) {
    _pattern_field* f = bsearch_pattern(pat, ctx->a[i].wire_id >> 3);
    if (f) {
      int index = (int)(f - pat->f);
      if (field[index] == false) {
        field[index] = true;
        ++fc;
        if ((f->ctype == CTYPE_ARRAY || f->ctype == CTYPE_PACKED)) {
          _pbc_array* array = (_pbc_array*)((char*)output + f->offset);
          _pbcA_open(array);
        }
      }
      char* out = (char*)output + f->offset;
      if (unpack_field(f->ctype, f->ptype, ctx->buffer, &ctx->a[i], out) < 0) {
        int j;
        for (j = 0; j < pat->count; j++) {
          if (field[j] == true && (pat->f[j].ctype == CTYPE_ARRAY || pat->f[j].ctype == CTYPE_PACKED)) {
            void* array = (char*)output + pat->f[j].offset;
            _pbcA_close((_pbc_array*)array);
          }
        }
        _pbcC_close(_ctx);
        pat->env->lasterror = "Pattern unpack field error";
        return -i - 1;
      }
    }
  }
  _pbcC_close(_ctx);
  if (fc != pat->count) {
    for (i = 0; i < pat->count; i++) {
      if (field[i] == false) {
        _pattern_set_default(&pat->f[i], (char*)output);
      }
    }
  }
  return 0;
}

/*
    format : key %type
    %f float
    %F double
    %d int32
    %D int64
    %b bool
    %h int16
    %c int8
    %s slice
    %a array
*/

static int _ctype(const char* ctype) {
  if (ctype[0] != '%')
    return -1;
  switch (ctype[1]) {
    case 'f':
      return CTYPE_FLOAT;
    case 'F':
      return CTYPE_DOUBLE;
    case 'd':
      return CTYPE_INT32;
    case 'D':
      return CTYPE_INT64;
    case 'b':
      return CTYPE_BOOL;
    case 'h':
      return CTYPE_INT16;
    case 'c':
      return CTYPE_INT8;
    case 's':
      return CTYPE_VAR;
    case 'a':
      return CTYPE_ARRAY;
    default:
      return -1;
  }
}

static int _ctype_size(const char* ctype) {
  switch (ctype[1]) {
    case 'f':
      return sizeof(float);
    case 'F':
      return sizeof(double);
    case 'd':
      return sizeof(int32_t);
    case 'D':
      return sizeof(int64_t);
    case 'b':
      return sizeof(bool);
    case 'h':
      return sizeof(int16_t);
    case 'c':
      return sizeof(int8_t);
    case 's':
      return sizeof(pbc_slice);
    case 'a':
      return sizeof(pbc_array);
    default:
      return 0;
  }
}

static const char* _copy_string(const char* format, char** temp) {
  char* output = *temp;
  while (*format == ' ' || *format == '\t' || *format == '\n' || *format == '\r') {
    ++format;
  }
  while (*format != '\0' && *format != ' ' && *format != '\t' && *format != '\n' && *format != '\r') {
    *output = *format;
    ++output;
    ++format;
  }
  *output = '\0';
  ++output;
  *temp = output;

  return format;
}

static int _scan_pattern(const char* format, char* temp) {
  int n = 0;
  for (;;) {
    format = _copy_string(format, &temp);
    if (format[0] == '\0')
      return 0;
    ++n;
    format = _copy_string(format, &temp);
    if (format[0] == '\0')
      return n;
  }
}

static int _comp_field(const void* a, const void* b) {
  const _pattern_field* fa = (const _pattern_field*)a;
  const _pattern_field* fb = (const _pattern_field*)b;

  return fa->id - fb->id;
}

pbc_pattern* _pbcP_new(pbc_env* env, int n) {
  size_t sz = sizeof(pbc_pattern) + (sizeof(_pattern_field)) * (n - 1);
  pbc_pattern* ret = (pbc_pattern*)_pbcM_malloc(sz);
  memset(ret, 0, sz);
  ret->count = n;
  ret->env = env;
  return ret;
}

static int _check_ctype(_field* field, _pattern_field* f) {
  if (field->label == LABEL_REPEATED) {
    return f->ctype != CTYPE_ARRAY;
  }
  if (field->label == LABEL_PACKED) {
    return f->ctype != CTYPE_PACKED;
  }
  if (field->type == PTYPE_STRING || field->type == PTYPE_MESSAGE || field->type == PTYPE_BYTES) {
    return f->ctype != CTYPE_VAR;
  }
  if (field->type == PTYPE_FLOAT || field->type == PTYPE_DOUBLE) {
    return !(f->ctype == CTYPE_DOUBLE || f->ctype == CTYPE_FLOAT);
  }
  if (field->type == PTYPE_ENUM) {
    return !(f->ctype == CTYPE_INT8 || f->ctype == CTYPE_INT8 || f->ctype == CTYPE_INT16 || f->ctype == CTYPE_INT32 ||
             f->ctype == CTYPE_INT64);
  }

  return f->ctype == CTYPE_VAR || f->ctype == CTYPE_ARRAY || f->ctype == CTYPE_PACKED || f->ctype == CTYPE_DOUBLE ||
         f->ctype == CTYPE_FLOAT;
}

pbc_pattern* _pattern_new(_message* m, const char* format) {
  size_t len = strlen(format);
  char* temp = (char*)alloca(len + 1);
  int n = _scan_pattern(format, temp);
  pbc_pattern* pat = _pbcP_new(m->env, n);
  int i;

  const char* ptr = temp;

  int offset = 0;

  for (i = 0; i < n; i++) {
    _pattern_field* f = &(pat->f[i]);
    _field* field = (_field*)_pbcM_sp_query(m->name, ptr);
    if (field == NULL) {
      m->env->lasterror = "Pattern @new query none exist field";
      goto _error;
    }
    f->id = field->id;
    f->ptype = field->type;
    *f->defv = *field->default_v;
    f->offset = offset;
    f->label = field->label;
    ptr += strlen(ptr) + 1;
    f->ctype = _ctype(ptr);
    if (f->ctype < 0) {
      m->env->lasterror = "Pattern @new use an invalid ctype";
      goto _error;
    }

    if (f->ctype == CTYPE_ARRAY && field->label == LABEL_PACKED) {
      f->ctype = CTYPE_PACKED;
    }
    if (_check_ctype(field, f)) {
      m->env->lasterror = "Pattern @new ctype check error";
      goto _error;
    }

    offset += _ctype_size(ptr);
    ptr += strlen(ptr) + 1;
  }

  pat->count = n;

  qsort(pat->f, n, sizeof(_pattern_field), _comp_field);
  return pat;
_error:
  _pbcM_free(pat);
  return NULL;
}

pbc_pattern* pbc_pattern_new(pbc_env* env, const char* message, const char* format, ...) {
  _message* m = _pbcP_get_message(env, message);
  if (m == NULL) {
    env->lasterror = "Pattern new can't find proto";
    return NULL;
  }
  if (format[0] == '@') {
    return _pattern_new(m, format + 1);
  }

  size_t len = strlen(format);
  char* temp = (char*)alloca(len + 1);
  int n = _scan_pattern(format, temp);
  pbc_pattern* pat = _pbcP_new(env, n);
  int i;
  va_list ap;
  va_start(ap, format);

  const char* ptr = temp;

  for (i = 0; i < n; i++) {
    _pattern_field* f = &(pat->f[i]);
    _field* field = (_field*)_pbcM_sp_query(m->name, ptr);
    if (field == NULL) {
      env->lasterror = "Pattern new query none exist field";
      goto _error;
    }
    f->id = field->id;
    f->ptype = field->type;
    *f->defv = *field->default_v;
    f->offset = va_arg(ap, int);
    f->label = field->label;

    ptr += strlen(ptr) + 1;

    f->ctype = _ctype(ptr);
    if (f->ctype < 0) {
      env->lasterror = "Pattern new use an invalid ctype";
      goto _error;
    }
    if (f->ctype == CTYPE_ARRAY && field->label == LABEL_PACKED) {
      f->ctype = CTYPE_PACKED;
    }
    if (_check_ctype(field, f)) {
      env->lasterror = "Pattern new ctype check error";
      goto _error;
    }

    ptr += strlen(ptr) + 1;
  }

  va_end(ap);

  pat->count = n;

  qsort(pat->f, n, sizeof(_pattern_field), _comp_field);
  return pat;
_error:
  _pbcM_free(pat);
  return NULL;
}

void pbc_pattern_delete(pbc_pattern* pat) {
  _pbcM_free(pat);
}

size_t pbc_pattern_memsize(pbc_pattern* pat) {
  int n = pat->count;
  return sizeof(pbc_pattern) + (sizeof(_pattern_field)) * (n - 1);
}
